MsgN("\n----------------------------------------")
MsgN("-- MadDog's Beams Loaded v1.01")
MsgN("----------------------------------------\n")

//holds all the beam settings
beams = {}

if (SERVER) then
	--include this file
	AddCSLuaFile("autorun/maddogs_beams.lua")

	beams.Settings = function( ent, beamMaterial, beamSize, beamColor )
		--add duplicator support
		duplicator.StoreEntityModifier( ent, "beamsettings", {
			m = beamMaterial,
			s = beamSize,
			c = beamColor
		})

		if (!ent.newbeams) then ent.newbeams = {} end

		local beamInfo = beamMaterial .. ";" .. beamSize .. ";" .. beamColor.r .. ";" .. beamColor.g .. ";" .. beamColor.b .. ";"

		--add new point with settings
		ent.newbeams[table.Count(ent.newbeams)+1] = {settings = beamInfo, ents = {}}

		data.Send("beams.Settings", ent, table.Count(ent.newbeams), beamInfo, "nocache") --send data with no cache

		--MsgN("beams.Settings")
	end

	--adv dup call
	function beams.SettingsDup( ply, ent, Data )
		if (!Data) then
			duplicator.ClearEntityModifier( ent, "beamsettings" );
		return end

		beams.Settings( ent, (Data.m or "cable/cable"), (Data.s or 2), Data.c or Color(255, 255, 255, 255))
	end
	duplicator.RegisterEntityModifier( "beamsettings", beams.SettingsDup )

	beams.Add = function( sEnt, fEnt, beamVec )
		duplicator.StoreEntityModifier( sEnt, "beamsadd", {
			e = fEnt,
			v = beamVec
		})

		if (!sEnt.newbeams) then
			--MsgN("No beam system for ", sEnt)
			return
		end

		sEnt.newbeams[table.Count(sEnt.newbeams)].ents[fEnt:EntIndex()] = beamVec --save the beam position (and constraint info)

		data.Send("beams.Add", sEnt, table.Count(sEnt.newbeams), fEnt:EntIndex(), beamVec, "nocache") --send the new table data to client

		if (fEnt!=sEnt) then
			if (!fEnt.newbeams) then fEnt.newbeams = {} end
			if (!fEnt.newbeams_constraints) then fEnt.newbeams_constraints = {} end

			fEnt.newbeams_constraints[sEnt:EntIndex()] = table.Count(sEnt.newbeams) --tell the other entity its being constrained by this one
		end

		--MsgN("beams.Add")
	end

	beams.Clear = function( self )
		--remove link from other devices constraint this entity
		for _entid, _rowid in pairs( self.newbeams_constraints or {} ) do --loop through all the saved beams
			local _ent = ents.GetByIndex(_entid)

			_ent.newbeams[_rowid].ents[self:EntIndex()] = nil	--remove self from the link
			if (newbeams_constraints) then _ent.newbeams_constraints[self:EntIndex()] = nil end

			data.Send("beams.Add", _ent, _rowid, self:EntIndex(), nil, "nocache") --send an update to remove
		end

		for _rowid, _info in pairs( self.newbeams ) do --loop through all the saved beams
			for _entid, _beamvec in pairs( _info.ents or {} ) do --loop through all the saved beams
				local _ent = ents.GetByIndex(_entid)

				if (_ent.newbeams_constraints) then _ent.newbeams_constraints[self:EntIndex()] = nil end

				--send message saying bye bye
				data.Send("beams.Add", _ent, _rowid, self:EntIndex(), nil, "nocache") --send the new table data to client
			end
		end

		--clear all the beam info and constraints
		self.newbeams_constraints = {}
		self.newbeams = {}

		data.Send("beams.Clear", self, "nocache")
	end

	--new system, work in progress
	beams.Connected = function(ent, first, _ents)
		local connected = {}
		_ents = _ents or {}

		_ents[ent:EntIndex()] = ent
--[[
		if (first) then
			MsgN("---------" , ent, "------------\nBeams:\n")

			PrintTable(ent.newbeams or {})

			MsgN("-----\nConstraints:\n")

			PrintTable(ent.newbeams_constraints or {})

			MsgN("-----")
		end
]]
		for _id, _info in pairs( ent.newbeams or {} ) do --loop through all the saved beams
			for _entid, _vecs in pairs( ent.newbeams[_id].ents ) do
				local beamEnt = ents.GetByIndex(_entid) --get beam data

				if (ValidEntity(beamEnt)) then
					if !_ents[_entid] then connected[_entid] = beamEnt end
				end
			end
		end

		for _entid, _rowid in pairs( ent.newbeams_constraints or {} ) do --loop through all the saved beams
			local _ent = ents.GetByIndex(_entid)
			--get entities linking to this entity
			--if _ent and ValidEntity(_ent) and

			if !_ents[_entid] then connected[_entid] = _ent end
		end

		for _, _ent in pairs( connected ) do --keep going down the line
			beams.Connected( _ent, false, _ents )
		end

		if (first == true) then
			--print(table.Count(_ents))
			return _ents
		end
	end




	beams.MaxDistance = 400

	beams.CheckDistance = function(self)
		if (!enable) then return end
		if (!self.newbeams_constraints) then return end

		if (!self.newbeams_warn) then self.newbeams_warn = {} end

		--new system, work in progress
		for _entid, _rowid in pairs( self.newbeams_constraints or {} ) do --loop through all the saved beams
			local _ent = ents.GetByIndex(_entid)

			if (!_ent or !ValidEntity(_ent)) then
				self.newbeams_constraints[_rowid] = nil --remove
				continue --next record please
			end

			local distance = (_ent:GetPos() - self:GetPos()):Length()

			if (distance > beams.MaxDistance) then
				self.newbeams_warn[_ent:EntIndex()] = (self.newbeams_warn[_ent:EntIndex()] or 0) + 1 --increase timer for this connection

				--5 chances to fix
				if (self.newbeams_warn[_ent:EntIndex()] >= 5) then
					self.newbeams_warn[_ent:EntIndex()] = nil --reset length warn count

					self.newbeams_constraints[_entid] = nil
					if (_ent.newbeams) then _ent.newbeams[_rowid].ents[self:EntIndex()] = nil end	--remove self from the link

					data.Send("beams.Add", _ent, _rowid, self:EntIndex(), nil, "nocache") --send an update to remove

					--break sound
					_ent:EmitSound("physics/metal/metal_computer_impact_bullet"..math.random(1,3)..".wav", 500)
					self:EmitSound("physics/metal/metal_computer_impact_bullet"..math.random(1,3)..".wav", 500)
				else
					local vol = 30 * self.newbeams_warn[_ent:EntIndex()]
					_ent:EmitSound("ambient/energy/newspark0"..math.random(1,9)..".wav", vol)
					self:EmitSound("ambient/energy/newspark0"..math.random(1,9)..".wav", vol)
				end
			else
				self.newbeams_warn[_ent:EntIndex()] = nil
			end
		end
	end





















	--adv dup call
	function beams.AddDup( ply, ent, Data )
		if (!Data) then
			duplicator.ClearEntityModifier( ent, "beamsadd" );
		return end

		beams.Add( ent, Data.e, Data.v )
	end
	duplicator.RegisterEntityModifier( "beamsadd", beams.AddDup )

	beams.BuildDupeInfo = function( self )
		--MsgN("beams.BuildDupeInfo")

--[[		local beams = table.Copy(self.beams)

		local intBeamPoints, intBeams, intContraints

		for intBeamPoints=1, (beams.BeamPoints or -1) do
			for intBeams=1, (beams["Beams"..intBeamPoints] or -1) do
				beams["BeamsEntity"..intBeamPoints..intBeams] = beams["BeamsEntity"..intBeamPoints..intBeams]:EntIndex()
			end
		end

		for intContraints=1, (beams.Contraints or -1) do
			beams[ "BeamsContraint"..intContraints ] = beams[ "BeamsContraint"..intContraints ]:EntIndex()
		end
]]
		--new system, work in progress
		local newbeams_constraints = table.Copy(self.newbeams_constraints)
		local beams = table.Copy(self.newbeams)

		for _rowid, _ent in pairs( newbeams_constraints or {} ) do
			newbeams_constraints[_rowid] = _ent:EntIndex() --convert entity to id
		end

		duplicator.StoreEntityModifier( self, "DupeBeamsInfo", {
			newbeams_constraints = newbeams_constraints,
			newbeams = beams
		})
	end

	beams.ApplyDupeInfo = function( self, info, GetEntByIDFunc )
		--MsgN("beams.ApplyDupeInfo")

	--[[
		--save beams to entity
		self.beams = info.beams

		local intBeamPoints, intBeams, intContraints

		for intBeamPoints=1, (self.beams.BeamPoints or -1) do
			--get beam info and explode into a table
			local beamInfo = string.Explode(";", self.beams["BeamInfo" .. intBeamPoints])

			--save the settings
			beams.Settings( self, (beamInfo[1] or "cable/rope_icon"), (beamInfo[2] or 2), Color( beamInfo[3] or 255, beamInfo[4] or 255, beamInfo[5] or 255, 255 ) )

			for intBeams=1, (self.beams["Beams"..intBeamPoints] or -1) do
				--convert from number to entity
				self.beams["BeamsEntity"..intBeamPoints..intBeams] = GetEntByIDFunc(self.beams["BeamsEntity"..intBeamPoints..intBeams])

				beams.Add( self, self.beams["BeamsEntity"..intBeamPoints..intBeams], self.beams["BeamsVector"..intBeamPoints..intBeams] )
			end
		end

		for intContraints=1, (self.beams.Contraints or -1) do
			self.beams[ "BeamsContraint"..intContraints ] = GetEntByIDFunc(self.beams[ "BeamsContraint"..intContraints ])
		end]]
	end
















end

if (CLIENT) then
	if (!beams.loaded) then
		--materails for links
		list.Add( "beams.Materials", "cable/rope_icon" )
		list.Add( "beams.Materials", "cable/cable2" )
		list.Add( "beams.Materials", "cable/xbeam" )
		list.Add( "beams.Materials", "cable/redlaser" )
		list.Add( "beams.Materials", "cable/blue_elec" )
		list.Add( "beams.Materials", "cable/physbeam" )
		list.Add( "beams.Materials", "cable/hydra" )
		list.Add( "beams.Materials", "cable/hose_black1" )
		list.Add( "beams.Materials", "sprites/orangelight1" )
		list.Add( "beams.Materials", "cable/new_cable_lit" )
		list.Add( "beams.Materials", "cable/cable_metalwinch01" )
		list.Add( "beams.Materials", "cable/cable" )
		list.Add( "beams.Materials", "vgui/progressbar" )


		--holds the materials
		beams.Materials = {}

		--preload materials
		for _, mat in pairs(list.Get( "beams.Materials" )) do
			beams.Materials[mat] = Material(mat)
		end
	end

	beams.Settings = function( ent, rowid, beamInfo )
		if (!ent.newbeams) then ent.newbeams = {} end

		if (!ent.MDBeams_oldDraw) then
			ent.MDBeams_oldDraw = ent.Draw

			function ent:Draw()
				ent:MDBeams_oldDraw()

				beams.Render( ent )
			end
		end

		ent.newbeams[rowid] = {settings = beamInfo, ents = {}}--create a new row for beam data
	end

	beams.Add = function( ent, rowid, entid, vector )
		if (!ent.newbeams || !ent.newbeams[rowid]) then return end

		if (!ent.newbeams[rowid].ents[entid]) then ent.newbeams[rowid].ents[entid] = {} end

		if (vector) then
			table.insert(ent.newbeams[rowid].ents[entid], vector)
		else
			ent.newbeams[rowid].ents[entid] = nil
		end

		beams.Render( ent, true )

		--MsgN("beams.Add: ", "-rowid: ", rowid, "-entid: ", entid, "-vector: ", vector)
	end

	beams.Clear = function( ent )
		ent.newbeams = {}
	end

	beams.Render = function( ent, nodraw )
		local bbmin, bbmax = false, false

		if (CurTime() >= (ent.NextBoundsUpdate or 0)) then
			--render bounds vars
			bbmin, bbmax = ent:OBBMins(), ent:OBBMaxs()

			ent.NextBoundsUpdate = CurTime() + 0.1 -- how often to update per second
		end

		if (!ent.newbeams) then return end --no beams dont render

		for _id, _info in pairs( ent.newbeams ) do --loop through all the saved beams

			if (table.Count(ent.newbeams[_id].ents) <= 1) then continue end --only need to render if we have a complete beam

			local beamInfo = string.Explode(";", _info.settings) --get beam info and explode into a table
			local beamMaterial, beamSize, beamColor = beams.Materials[beamInfo[1] or "cable/rope_icon"], beamInfo[2] or 2, Color( beamInfo[3] or 255, beamInfo[4] or 255, beamInfo[5] or 255, 255 ) --get beam info from table (1: Material 2: beamSize 3: beamR 4: beamG 5: beamB 6: beamAlpha)

			if (beamSize==0) then continue end --dont draw, since beam size is zero

			local i, scroll, start = 1, CurTime() * 0.5, nil --make some vars we are about to use

			if (!nodraw && beamSize!=0) then -- start the render process
				local total = 0

				for _entid, _vecs in pairs( ent.newbeams[_id].ents ) do
					for _, _entvec in pairs( _vecs ) do
						total = total + 1
					end
				end

				render.SetMaterial( beamMaterial )
				render.StartBeam( total )	--how many links (points) the beam has
			end

			for _entid, _vecs in pairs( ent.newbeams[_id].ents ) do
				for _, _entvec in pairs( _vecs ) do
					if (!start) then start = _entvec end --set start point

					local beamPos, beamEnt = _entvec, ents.GetByIndex(_entid) --get beam data

					if (!ValidEntity(beamEnt)) then
						ent.newbeams[_id].ents[_entid] = nil --clear this record
					else
						local pos = beamEnt:LocalToWorld(beamPos) --get beam world vector

						if (bbmin) then
							local nodepos = ent:WorldToLocal(beamEnt:LocalToWorld(beamPos))

							if (nodepos.x < bbmin.x) then bbmin.x = nodepos.x end
							if (nodepos.y < bbmin.y) then bbmin.y = nodepos.y end
							if (nodepos.z < bbmin.z) then bbmin.z = nodepos.z end
							if (nodepos.x > bbmax.x) then bbmax.x = nodepos.x end
							if (nodepos.y > bbmax.y) then bbmax.y = nodepos.y end
							if (nodepos.z > bbmax.z) then bbmax.z = nodepos.z end
						end

						scroll = scroll - (pos-start):Length()/10 --update scroll
						if (!nodraw && beamSize!=0) then render.AddBeam(pos, beamSize, scroll, beamColor) end -- add point
						start = pos --reset start postion
					end
				end
			end

			if (!nodraw && beamSize!=0) then render.EndBeam() end --render done
		end

		if (bbmin && bbmax) && ((bbmin != ent.bbmin) || (bbmax != ent.bbmax)) then
			ent:SetRenderBounds(bbmin, bbmax)

			ent.bbmin = bbmin
			ent.bbmax = bbmax
		end
	end
end